"use strict";
//http://mongodb.github.io/node-mongodb-native/api-articles/nodekoarticle1.html

var MongoClient = require('mongodb').MongoClient,
    format = require('util').format,
    GridStore = require('mongodb').GridStore,
    Q = require('q');


function MongoService() {
    console.log("MongoService constructor");
    //hask to serialize Regex 
    RegExp.prototype.toJSON = RegExp.prototype.toString;

    //this.connectionString = "mongodb://jmb-amazone:jmb-amazone@ds045099.mongolab.com:45099/jmb-amazone";
    //this.connectionString = "mongodb://jmb:jmb@ds043997.mongolab.com:43997/jmb";
    this.connectionString = "mongodb://localhost:27017/jmb";
}

MongoService.prototype = {
    connect: function () {
        if (this.db) {
            var self = this;
            return Q.fcall(function () {
                return self.db;
            });
        }
        else return this._init();
    },
    _init: function () {
        var self = this;
        return Q.nfcall(MongoClient.connect, self.connectionString).then(function (db) {
            self.db = db;
            console.log("connected @ " + self.connectionString);
            return db;
        });
    },
    insert: function (collectionName, item) {
        return this.connect().then(function (db) {
            return Q.ninvoke(db.collection(collectionName), 'insert', item).then(function (items) {
                items.forEach(function (i) {
                    console.log("db." + collectionName + ".insert(" + JSON.stringify(i) + ")");
                });
                return items;
            });
        });
    },
    remove: function (collectionName, selector) {
        console.log("remove");
        return this.connect().then(function (db) {
            console.log("db." + collectionName + ".remove(" + JSON.stringify(selector) + ")");
            return Q.ninvoke(db.collection(collectionName), 'remove', selector);
        });
    },
    update: function (collectionName, criteria, update, options) {
        options = options || {};
        return this.connect().then(function (db) {
            return Q.ninvoke(db.collection(collectionName), 'update', criteria, update, options).then(function (res) {
                console.log("db." + collectionName + ".update(" + JSON.stringify(criteria) + ", " + JSON.stringify(update) + ", " + JSON.stringify(options), ") => " + res[0] + " element(s).");
                return res;
            });
        });
    },
    findOne: function (collectionName, query, fields) {
        fields = fields || {};
        return this.connect().then(function (db) {
            console.log("db." + collectionName + ".findOne(" + JSON.stringify(query) + ", " + JSON.stringify(fields) + ")");
            return Q.ninvoke(db.collection(collectionName), 'findOne', query, fields);
        });
    },
    find: function (collectionName, query, options) {
        options = options || {};
        return this.connect().then(function (db) {
            return Q.ninvoke(db.collection(collectionName), 'find', query, options).then(function (cursor) {
                return Q.ninvoke(cursor, 'count').then(function (count) {
                    console.log("db." + collectionName + ".find(" + JSON.stringify(query) + ", " + JSON.stringify(options) + ") => " + count + " elements.");
                    cursor.rewind();
                    return cursor;
                });
            });
        });
    },
    //http://emptysqua.re/blog/paging-geo-mongodb/ for skip
    geoNear: function (collectionName, x, y, options) {
        return this.connect().then(function (db) {

            var command = {
                geoNear: collectionName,
                near: {
                    type: "Point",
                    coordinates: [x, y]
                }
            };

            if (options.spherical) command.spherical = options.spherical;
            if (options.query) command.query = options.query;
            if (options.maxDistance) command.maxDistance = options.maxDistance;
            if (options.minDistance) command.minDistance = options.minDistance;
            if (options.distanceMultiplier) command.distanceMultiplier = options.distanceMultiplier;
            if (options.num) command.num = options.num;

            var deferred = Q.defer();
            db.command(command, function (error, res) {
                if (error) {
                    deferred.reject(new Error(error + " " + JSON.stringify(command)));
                } else {
                    deferred.resolve(res);
                }
            });

            return deferred.promise.then(function (doc) {
                console.log("db.runCommand(" + JSON.stringify(command) + ") => " + doc.results.length + " element(s).");
                return doc;
            });
        })

        //console.log("db." + collectionName + ".geoNear(" + x + ", " + y + ", " + JSON.stringify(options) + ")");
        //return Q.ninvoke(db.collection(collectionName), 'geoNear', x, y, options);
    },
    aggregate: function (collectionName, array) {
        return this.connect().then(function (db) {
            console.log("db." + collectionName + ".aggregate(" + JSON.stringify(array) + ")");
            return Q.ninvoke(db.collection(collectionName), 'aggregate', array);
        });
    },
    mapReduce: function (collectionName, map, reduce, options) {
        return this.connect().then(function (db) {
            console.log("db." + collectionName + ".mapReduce(" + map + ", " + reduce + ", " + JSON.stringify(options) + ")");
            return Q.ninvoke(db.collection(collectionName), 'mapReduce', map, reduce, options)
        });
    },
    gridStore: function (objectId, mode, options) {
        options = options || {};
        return this.connect().then(function (db) {
            console.log("new GridStore(db, " + objectId + ", " + mode + ", " + JSON.stringify(options) + ")");
            return new GridStore(db, objectId, mode, options);
        });
    },
    each: function (result) {
        var deferred = Q.defer();
        result.each(function (err, data) {
            if (err) {
                deferred.reject(err);
            } else if (data == null) {
                deferred.resolve([]); // end of the stream
            } else {
                var nextDeferred = Q.defer();
                deferred.resolve(nextDeferred.promise.spread(function (rest) {
                    return [data].concat(rest);
                }));
                deferred = nextDeferred;
            }
        })
        return deferred.promise;
    },
    ensureIndex: function (collectionName, index) {
        return this.connect().then(function (db) {
            return Q.ninvoke(db.collection(collectionName), 'ensureIndex', index);
        });
    },
    createCollection: function (name) {
        return this.connect().then(function (db) {
            return Q.ninvoke(db, 'createCollection', name);
        });
    }
};

exports = module.exports = new MongoService();

